﻿using Odyssey.BouncyCastle.SMUtil;
using Org.BouncyCastle.Asn1;
using Org.BouncyCastle.Asn1.X509;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Generators;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Crypto.Tls;
using Org.BouncyCastle.Math;
using Org.BouncyCastle.Math.EC;
using Org.BouncyCastle.Security;
using Org.BouncyCastle.Utilities;
using Org.BouncyCastle.X509;
using System;
using System.Collections.Generic;
using System.Text;

namespace Odyssey.BouncyCastle.SMUtil.Implementations
{
    public class SMDispatch : ISMDispatch
    {
        private ECPrivateKeyParameters ecPriv;
        private ECPublicKeyParameters ecPub;
        private ECPoint g;
        private string UserId;
        private int _byteLen;


        public SMDispatch(string userId, string privateKey, string publicKey = "")
        {
            UserId = userId;
            BigInteger SM2_ECC_P = new BigInteger("FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF", 16);
            BigInteger SM2_ECC_A = new BigInteger("FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFC", 16);
            BigInteger SM2_ECC_B = new BigInteger("28E9FA9E9D9F5E344D5A9E4BCF6509A7F39789F515AB8F92DDBCBD414D940E93", 16);
            BigInteger SM2_ECC_N = new BigInteger("FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123", 16);
            BigInteger SM2_ECC_H = BigInteger.ValueOf(4);
            BigInteger SM2_ECC_GX = new BigInteger("32C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7", 16);
            BigInteger SM2_ECC_GY = new BigInteger("BC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0", 16);

            ECCurve curve = new FpCurve(SM2_ECC_P, SM2_ECC_A, SM2_ECC_B, SM2_ECC_N, SM2_ECC_H);

            g = curve.CreatePoint(SM2_ECC_GX, SM2_ECC_GY);
            ECDomainParameters domainParams = new ECDomainParameters(curve, g, SM2_ECC_N);

            #region 重建私钥
            if (!string.IsNullOrWhiteSpace(privateKey))
            {
                //BigInteger bi = new BigInteger(HexStrToToByte(privateKey));
                BigInteger bi = new BigInteger(privateKey,16);
                ecPriv = new ECPrivateKeyParameters(bi, domainParams);
            }

            #endregion


            #region 重建公钥
            if (!string.IsNullOrWhiteSpace(publicKey))
            {
                //byte[] pc = HexStrToToByte("04" + publicKey); //只处理未压缩的公钥
                //var basePoint = TlsEccUtilities.ValidateECPublicKey(TlsEccUtilities.DeserializeECPublicKey(null, domainParams, pc));
                //SubjectPublicKeyInfo subinfo = SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(basePoint);
                //ecPub = (ECPublicKeyParameters)PublicKeyFactory.CreateKey(subinfo);
                if (publicKey.Length==128)
                {
                    //只处理未压缩的公钥
                    var publicKeyPoint = curve.CreatePoint(new BigInteger(publicKey.Substring(0, 64), 16), new BigInteger(publicKey.Substring(64, 64), 16));
                    ecPub = new ECPublicKeyParameters(publicKeyPoint, domainParams);
                }
                else if (publicKey.Length==66)
                {

                }
               
            }
            #endregion

            this._byteLen = (int)Math.Ceiling((double)(((double)curve.FieldSize) / 8.0));
        }

        /// <summary>
        /// 解密
        /// </summary>
        /// <param name="data"></param>
        /// <param name="padding"></param>
        /// <returns></returns>
        public string Decrypt(string data )
        {
            throw new NotImplementedException();
        }



        /// <summary>
        /// 加密
        /// </summary>
        /// <param name="data"></param>
        /// <param name="padding"></param>
        /// <returns></returns>
        public string Encrypt(string data )
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// 签名
        /// </summary>
        /// <param name="data"></param>
        /// <param name="hashAlgorithmName"></param>
        /// <param name="padding"></param>
        /// <returns></returns>
        public string Sign(string data)
        {
            byte[] sig = SignBytes( data);


            BigInteger[] rs = Decode(sig);
        
            string signHexStr = HexUtil.ToHexString(BigIntegerToByteArray(rs[0]), HexUtil.SpacePosition.Back) + HexUtil.ToHexString(BigIntegerToByteArray(rs[1]), HexUtil.SpacePosition.Back);

            //Asn1Sequence s = Asn1Sequence.GetInstance(sig);

            //byte[] rb = DerInteger.GetInstance(s[0]).Value.ToByteArrayUnsigned();
            //byte[] sb = DerInteger.GetInstance(s[1]).Value.ToByteArrayUnsigned();

            //string signHexStr = HexUtil.ToHexString(rb, HexUtil.SpacePosition.Back) + HexUtil.ToHexString(sb, HexUtil.SpacePosition.Back);



            //string signHexStr = byteToHexStr(rb, rb.Length) + byteToHexStr(sb, sb.Length);   

            return signHexStr;
        }

        private static BigInteger[] Decode(byte[] sig)
        {
            Asn1Sequence s = Asn1Sequence.GetInstance(sig);

            return new BigInteger[] { DerInteger.GetInstance(s[0]).Value,
                DerInteger.GetInstance(s[1]).Value };
        }

        private byte[] BigIntegerToByteArray(BigInteger bi)
        {
            return BigIntegerToByteArray(bi, this._byteLen);
        }

        private static byte[] BigIntegerToByteArray(BigInteger bi,int byteLength=0)
        {
            byte[] sourceArray = bi.ToByteArrayUnsigned();
            if (sourceArray.Length >= byteLength)
            {
                return sourceArray;
            }
            byte[] destinationArray = new byte[byteLength];
            Array.Copy(sourceArray, 0, destinationArray, byteLength - sourceArray.Length, sourceArray.Length);
            return destinationArray;
        }

        public byte[] SignBytes(string data )
        {
            if (ecPriv == null)
            {
                throw new Exception("no ecPriv");
            }
            SM2 signer = new SM2();

            signer.Init(true,
               new ParametersWithID(new ParametersWithRandom(ecPriv, new SecureRandom()), Strings.ToByteArray(this.UserId)));
            //new ParametersWithID(new ParametersWithRandom(ecPriv, CreateFixedSecureRandom("6CB28D99385C175C94F94E934817663FC176D925DD72B727260DBAAE1FB2F96F")), Strings.ToByteArray(this.UserId)));

            byte[] msg = HexStrToToByte(data);

            signer.BlockUpdate(msg, 0, msg.Length);

            byte[] sig = signer.GenerateSignature();

            System.Console.Write("1");
            return sig;
        }

        public static string CompressPubKey(string longPK)
        {
            if (string.IsNullOrWhiteSpace(longPK) || longPK.Length != 128)
            {
                throw new Exception("public key length is not 128!");
            }

            ECCurve curve = GetBaseCurve();
            //byte[] longPKbt = HexStrToToByte("04" + longPK);
            //ECPoint g = curve.DecodePoint(longPKbt);
            //string compressPK = byteToHexStr(g.GetEncoded(true));
            var publicKeyPoint = curve.CreatePoint(new BigInteger(longPK.Substring(0, 64), 16), new BigInteger(longPK.Substring(64, 64), 16), true);
            string compressPK = HexUtil.ToHexString(publicKeyPoint.GetEncoded(true), HexUtil.SpacePosition.Back);
            return compressPK;
        }

        public static string UnCompressPubKey(string compressedPK)
        {
            if (string.IsNullOrWhiteSpace(compressedPK) || compressedPK.Length != 66)
            {
                throw new Exception("public key length is not 66!");
            }
            ECCurve curve = GetBaseCurve();
            byte[] compressedPKbt = HexStrToToByte(compressedPK);
            ECPoint eee = curve.DecodePoint(compressedPKbt);
            //string longPK = eee.XCoord.ToString().ToUpper() + eee.YCoord.ToString().ToUpper();
            int bytelength = (int)Math.Ceiling((double)(((double)curve.FieldSize) / 8.0));
            string longPK = HexUtil.ToHexString(BigIntegerToByteArray(eee.XCoord.ToBigInteger(), bytelength), HexUtil.SpacePosition.Back) + HexUtil.ToHexString(BigIntegerToByteArray(eee.YCoord.ToBigInteger(), bytelength), HexUtil.SpacePosition.Back);

            return longPK;
        }


        /// <summary>
        /// 验签
        /// </summary>
        /// <param name="data"></param>
        /// <param name="sign"></param>
        /// <param name="hashAlgorithmName"></param>
        /// <param name="padding"></param>
        /// <returns></returns>
        public bool VerifySign(string data, string sign)
        {
            if (ecPub == null)
            {
                throw new Exception("SM Verify failed, no ecPriv");
            }

            SM2 signer = new SM2();
            signer.Init(false, new ParametersWithID(ecPub, Strings.ToByteArray(this.UserId)));

            byte[] msg = HexStrToToByte(data);
            signer.BlockUpdate(msg, 0, msg.Length);

            BigInteger rbb = new BigInteger(sign.Substring(0, 64), 16);
            BigInteger sbb = new BigInteger(sign.Substring(64, 64), 16);

            bool res = signer.VerifySignature(rbb, sbb);

            System.Console.Write("2");
            return res;
        }

        public static List<string> GeneratKeys()
        {
            BigInteger SM2_ECC_P = new BigInteger("FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF", 16);
            BigInteger SM2_ECC_A = new BigInteger("FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFC", 16);
            BigInteger SM2_ECC_B = new BigInteger("28E9FA9E9D9F5E344D5A9E4BCF6509A7F39789F515AB8F92DDBCBD414D940E93", 16);
            BigInteger SM2_ECC_N = new BigInteger("FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123", 16);
            BigInteger SM2_ECC_H = BigInteger.ValueOf(4);
            BigInteger SM2_ECC_GX = new BigInteger("32C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7", 16);
            BigInteger SM2_ECC_GY = new BigInteger("BC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0", 16);

            ECCurve curve = new FpCurve(SM2_ECC_P, SM2_ECC_A, SM2_ECC_B, SM2_ECC_N, SM2_ECC_H);

            ECPoint   g1 = curve.CreatePoint(SM2_ECC_GX, SM2_ECC_GY);

            ECDomainParameters domainParams = new ECDomainParameters(curve, g1, SM2_ECC_N);  

            ECKeyGenerationParameters keyGenerationParams = new ECKeyGenerationParameters(domainParams, new SecureRandom());
            ECKeyPairGenerator keyPairGenerator = new ECKeyPairGenerator();

            keyPairGenerator.Init(keyGenerationParams);
            AsymmetricCipherKeyPair kp = keyPairGenerator.GenerateKeyPair();

            ECPrivateKeyParameters ecPriv1 = (ECPrivateKeyParameters)kp.Private;
            ECPublicKeyParameters ecPub1 = (ECPublicKeyParameters)kp.Public;

            int bytelength=(int)Math.Ceiling((double)(((double)curve.FieldSize) / 8.0));

            List<string> keys = new List<string>();
            byte[] prikByte = BigIntegerToByteArray( ecPriv1.D, bytelength);
            
            keys.Add(HexUtil.ToHexString(prikByte, HexUtil.SpacePosition.Back));
            //keys.Add(ecPub1.Q.XCoord.ToString().ToUpper() + ecPub1.Q.YCoord.ToString().ToUpper());
            keys.Add(HexUtil.ToHexString(BigIntegerToByteArray(ecPub1.Q.XCoord.ToBigInteger(), bytelength), HexUtil.SpacePosition.Back) + HexUtil.ToHexString(BigIntegerToByteArray(ecPub1.Q.YCoord.ToBigInteger(), bytelength), HexUtil.SpacePosition.Back));

            //Encoding.Default.GetString(Hex.Encode(ecPriv1.D.ToByteArray())).ToUpper()
            //Encoding.Default.GetString(Hex.Encode(ecPub1.Q.GetEncoded())).ToUpper()
               
            return keys;
        }

        private static ECCurve GetBaseCurve()
        {
            BigInteger SM2_ECC_P = new BigInteger("FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF", 16);
            BigInteger SM2_ECC_A = new BigInteger("FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFC", 16);
            BigInteger SM2_ECC_B = new BigInteger("28E9FA9E9D9F5E344D5A9E4BCF6509A7F39789F515AB8F92DDBCBD414D940E93", 16);
            BigInteger SM2_ECC_N = new BigInteger("FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123", 16);
            BigInteger SM2_ECC_H = BigInteger.ValueOf(4);
            return new FpCurve(SM2_ECC_P, SM2_ECC_A, SM2_ECC_B, SM2_ECC_N, SM2_ECC_H);
        }

        public void Dispose()
        {
            
        }


        private static byte[] HexStrToToByte(string hexString)
        {
            hexString = hexString.Replace(" ", "");
            if ((hexString.Length % 2) != 0)
                hexString += " ";
            byte[] returnBytes = new byte[hexString.Length / 2];
            for (int i = 0; i < returnBytes.Length; i++)
                returnBytes[i] = Convert.ToByte(hexString.Substring(i * 2, 2), 16);
            return returnBytes;
        }

        private static string byteToHexStr(byte[] bytes)
        {
            return byteToHexStr(bytes, bytes.Length);
        }
        private static string byteToHexStr(byte[] bytes, int length)
        {
            System.Text.StringBuilder sb = new System.Text.StringBuilder();
            if (bytes != null)
            {
                for (int i = 0; i < length; i++)
                {
                    sb.Append(bytes[i].ToString("X2"));
                }
            }
            return sb.ToString();
        }
    }
}
